using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection;
using System.Text;
using System.Windows.Forms;

using PerlinNoise;
using PerlinNoise.NoiseGenerator;
using PerlinNoise.Interpolator;
using PerlinNoise.Smoother;

namespace PerlinNoise.Test
{
    delegate UnproperControl? AreParametersProperDelegate();
    delegate void DrawFrameDelegate();

    public partial class mainForm : Form
    {
        private static readonly Range4D DEFAULT_RANGES;
        private static readonly uint DEFAULT_POINTS_NUMBER;
        private static readonly double ANIMATION_DEFAULT_STEP_VALUE;
        private static readonly uint ANIMATION_DEFAULT_FPS;

        private AbstractPerlinNoise perlinNoise;
        private AbstractNoiseGenerator noiseGenerator;
        private AbstractInterpolator interpolator;
        private AbstractSmoother smoother;
        private readonly Dictionary<int, AbstractPerlinNoiseOperator> perlinNoiseOperators;
        private readonly Dictionary<int, Type> noiseGeneratorTypes;
        private readonly Dictionary<int, Type> interpolatorTypes;
        private readonly Dictionary<int, Type> smootherTypes;

        private Range4D valuesRange;
        private uint pointsNumber;

        private double animationCurrentStepValue;
        private double animationStepValue;
        private Timer animationTimer;
        private int drawnFramesCounter;
        private int checkDrawnFramesCounter;
        private long animationStartTicksCounter;
        private TimeSpan lastCheckTimeSpan;
        private double currentFPS;

        private Button currentGenerateButton;
        private AreParametersProperDelegate areParametersProper;
        private DrawFrameDelegate perlinNoiseDrawFrame;

        private Bitmap perlinNoise1DTextureBuffer;
        private Bitmap perlinNoise1DGraphBuffer;
        private Bitmap perlinNoise2DTextureBuffer;
        private Bitmap perlinNoise2DTextureOnSphereBuffer;
        private Bitmap perlinNoise2D1DGraphBuffer;
        private Bitmap perlinNoise3DTextureBuffer;
        private Bitmap perlinNoise3D2DTextureBuffer;

        static mainForm()
        {
            DEFAULT_RANGES = new Range4D(new Range(0.0d, 10.0d),
                new Range(0.0d, 10.0d),
                new Range(0.0d, 10.0d),
                new Range(0.0d, 10.0d));
            DEFAULT_POINTS_NUMBER = 50;
            ANIMATION_DEFAULT_STEP_VALUE = 0.1d;
            ANIMATION_DEFAULT_FPS = 60;
        }

        public mainForm()
        {
            // Initialize fields
            this.perlinNoise = null;
            this.noiseGenerator = null;
            this.interpolator = null;
            this.smoother = null;

            this.valuesRange = DEFAULT_RANGES;
            this.pointsNumber = DEFAULT_POINTS_NUMBER;

            // Initialize timers
            this.animationCurrentStepValue = 0.0d;
            this.animationStepValue = ANIMATION_DEFAULT_STEP_VALUE;
            this.animationTimer = new Timer();
            this.animationTimer.Tick += new EventHandler(this.animationTimerTick);

            // Initialize components
            InitializeComponent();

            // Initialize perlin noise types
            this.perlinNoiseOperators = new Dictionary<int, AbstractPerlinNoiseOperator>();
            this.perlinNoiseOperators.Add(this.perlinNoiseTypeComboBox.Items.Add("Simple"), new SimplePerlinNoiseOperator());
            this.perlinNoiseOperators.Add(this.perlinNoiseTypeComboBox.Items.Add("Summed"), new SummedPerlinNoiseOperator());
            this.perlinNoiseTypeComboBox.SelectedIndex = 1;

            // Initialize noise generator types
            this.noiseGeneratorTypes = new Dictionary<int, Type>();
            this.noiseGeneratorTypes.Add(this.noiseGeneratorComboBox.Items.Add("Prime"), typeof(PrimeNoiseGenerator));
            this.noiseGeneratorTypes.Add(this.noiseGeneratorComboBox.Items.Add("Index table"), typeof(IndexTableNoiseGenerator));
            this.noiseGeneratorComboBox.SelectedIndex = 1;

            // Initialize interpolator types
            this.interpolatorTypes = new Dictionary<int, Type>();
            this.interpolatorTypes.Add(this.interpolatorComboBox.Items.Add("None"), null);
            this.interpolatorTypes.Add(this.interpolatorComboBox.Items.Add("Linear"), typeof(LinearInterpolator));
            this.interpolatorTypes.Add(this.interpolatorComboBox.Items.Add("Cosine"), typeof(CosineInterpolator));
            this.interpolatorTypes.Add(this.interpolatorComboBox.Items.Add("Cubic"), typeof(CubicInterpolator));
            this.interpolatorComboBox.SelectedIndex = 2;

            // Initialize smoother types
            this.smootherTypes = new Dictionary<int, Type>();
            this.smootherTypes.Add(this.smootherComboBox.Items.Add("None"), null);
            this.smootherTypes.Add(this.smootherComboBox.Items.Add("Wage"), typeof(WageSmoother));
            this.smootherComboBox.SelectedIndex = 0;

            // Set default values for components
            this.perlinNoise1DStartValueText.Text = valuesRange.X.Start.ToString();
            this.perlinNoise1DEndValueText.Text = valuesRange.X.End.ToString();
            this.perlinNoise2DStartXValueText.Text = valuesRange.X.Start.ToString();
            this.perlinNoise2DEndXValueText.Text = valuesRange.X.End.ToString();
            this.perlinNoise2DStartYValueText.Text = valuesRange.Y.Start.ToString();
            this.perlinNoise2DEndYValueText.Text = valuesRange.Y.End.ToString();
            this.perlinNoise3DStartXValueText.Text = valuesRange.X.Start.ToString();
            this.perlinNoise3DEndXValueText.Text = valuesRange.X.End.ToString();
            this.perlinNoise3DStartYValueText.Text = valuesRange.Y.Start.ToString();
            this.perlinNoise3DEndYValueText.Text = valuesRange.Y.End.ToString();
            this.perlinNoise3DStartZValueText.Text = valuesRange.Z.Start.ToString();
            this.perlinNoise3DEndZValueText.Text = valuesRange.Z.End.ToString();
            this.perlinNoise1DPointsNumberText.Text = pointsNumber.ToString();
            this.perlinNoise2DPointsNumberText.Text = pointsNumber.ToString();
            this.perlinNoise3DPointsNumberText.Text = pointsNumber.ToString();
            this.perlinNoise1DGenerateTextureCheckBox.Checked = true;
            this.perlinNoise1DGenerateGraphCheckBox.Checked = true;
            this.perlinNoise2DGenerateTextureCheckBox.Checked = true;
            this.perlinNoise2DGenerateTextureOnSphereCheckBox.Checked = true;
            this.perlinNoise2DGenerate1DGraphCheckBox.Checked = true;
            this.perlinNoise3DGenerateTextureCheckBox.Checked = true;
            this.perlinNoise3DGenerate2DTextureCheckBox.Checked = true;
            this.animationCurrentStepValueText.Text = animationCurrentStepValue.ToString();
            this.animationStepValueText.Text = animationStepValue.ToString();
            this.animationFPSText.Text = ANIMATION_DEFAULT_FPS.ToString();
            this.immediateUpdateCheckBox.Checked = false;

            // Set default value for current controlling components
            this.currentGenerateButton = this.perlinNoise1DGenerateButton;
            this.areParametersProper = this.arePN1DParametersProper;
            this.perlinNoiseDrawFrame = this.perlinNoise1DDrawFrame;

            // Create buffers
            this.perlinNoise1DTextureBuffer = new Bitmap(this.perlinNoise1DDrawTexturePanel.Width, this.perlinNoise1DDrawTexturePanel.Height, PixelFormat.Format24bppRgb);
            this.perlinNoise1DGraphBuffer = new Bitmap(this.perlinNoise1DDrawGraphPanel.Width, this.perlinNoise1DDrawGraphPanel.Height, PixelFormat.Format24bppRgb);
            this.perlinNoise2DTextureBuffer = new Bitmap(this.perlinNoise2DDrawTexturePanel.Width, this.perlinNoise2DDrawTexturePanel.Height, PixelFormat.Format24bppRgb);
            this.perlinNoise2DTextureOnSphereBuffer = new Bitmap(this.perlinNoise2DDrawTextureOnSpherePanel.Width, this.perlinNoise2DDrawTextureOnSpherePanel.Height, PixelFormat.Format24bppRgb);
            this.perlinNoise2D1DGraphBuffer = new Bitmap(this.perlinNoise2DDrawTextureOnSpherePanel.Width, this.perlinNoise2DDrawTextureOnSpherePanel.Height, PixelFormat.Format24bppRgb);
            this.perlinNoise3DTextureBuffer = new Bitmap(this.perlinNoise3DDrawTexturePanel.Width, this.perlinNoise3DDrawTexturePanel.Height, PixelFormat.Format24bppRgb);
            this.perlinNoise3D2DTextureBuffer = new Bitmap(this.perlinNoise3DDraw2DTexturePanel.Width, this.perlinNoise3DDraw2DTexturePanel.Height, PixelFormat.Format24bppRgb);
        }

        #region Parameter checkers
        private UnproperControl? arePN1DParametersProper()
        {
            UnproperControl? unproperControl = null;

            try {
                this.valuesRange.X.Start = double.Parse(this.perlinNoise1DStartValueText.Text);
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.perlinNoise1DStartValueText,
                    "Start value should be floating point number");
            }

            try {
                this.valuesRange.X.End = double.Parse(this.perlinNoise1DEndValueText.Text);
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.perlinNoise1DEndValueText,
                    "End value should be floating point number");
            }

            try {
                this.pointsNumber = uint.Parse(this.perlinNoise1DPointsNumberText.Text);
                if (this.pointsNumber < 2) {
                    throw new FormatException();
                }
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.perlinNoise1DPointsNumberText,
                    "End value should be integer number greater or equal 2");
            }

            try {
                this.animationCurrentStepValue = double.Parse(this.animationCurrentStepValueText.Text);
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.animationCurrentStepValueText,
                    "Current step value should be floating point number");
            }

            return unproperControl;
        }

        private UnproperControl? arePN2DParametersProper()
        {
            UnproperControl? unproperControl = null;

            try {
                this.valuesRange.X.Start = double.Parse(this.perlinNoise2DStartXValueText.Text);
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.perlinNoise2DStartXValueText,
                    "Start value should be floating point number");
            }

            try {
                this.valuesRange.X.End = double.Parse(this.perlinNoise2DEndXValueText.Text);
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.perlinNoise2DEndXValueText,
                    "End value should be floating point number");
            }

            try {
                this.valuesRange.Y.Start = double.Parse(this.perlinNoise2DStartYValueText.Text);
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.perlinNoise2DStartYValueText,
                    "Start value should be floating point number");
            }

            try {
                this.valuesRange.Y.End = double.Parse(this.perlinNoise2DEndYValueText.Text);
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.perlinNoise2DEndYValueText,
                    "End value should be floating point number");
            }

            try {
                this.pointsNumber = uint.Parse(this.perlinNoise2DPointsNumberText.Text);
                if (this.pointsNumber < 2) {
                    throw new FormatException();
                }
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.perlinNoise2DPointsNumberText,
                    "End value should be integer number greater or equal 2");
            }

            try {
                this.animationCurrentStepValue = double.Parse(this.animationCurrentStepValueText.Text);
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.animationCurrentStepValueText,
                    "Current step value should be floating point number");
            }

            return unproperControl;
        }

        private UnproperControl? arePN3DParametersProper()
        {
            UnproperControl? unproperControl = null;

            try {
                this.valuesRange.X.Start = double.Parse(this.perlinNoise3DStartXValueText.Text);
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.perlinNoise3DStartXValueText,
                    "Start value should be floating point number");
            }

            try {
                this.valuesRange.X.End = double.Parse(this.perlinNoise3DEndXValueText.Text);
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.perlinNoise3DEndXValueText,
                    "End value should be floating point number");
            }

            try {
                this.valuesRange.Y.Start = double.Parse(this.perlinNoise3DStartYValueText.Text);
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.perlinNoise3DStartYValueText,
                    "Start value should be floating point number");
            }

            try {
                this.valuesRange.Y.End = double.Parse(this.perlinNoise3DEndYValueText.Text);
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.perlinNoise3DEndYValueText,
                    "End value should be floating point number");
            }

            try {
                this.valuesRange.Z.Start = double.Parse(this.perlinNoise3DStartZValueText.Text);
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.perlinNoise3DStartZValueText,
                    "Start value should be floating point number");
            }

            try {
                this.valuesRange.Z.End = double.Parse(this.perlinNoise3DEndZValueText.Text);
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.perlinNoise3DEndZValueText,
                    "End value should be floating point number");
            }

            try {
                this.pointsNumber = uint.Parse(this.perlinNoise3DPointsNumberText.Text);
                if (this.pointsNumber < 2) {
                    throw new FormatException();
                }
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.perlinNoise3DPointsNumberText,
                    "End value should be integer number greater or equal 2");
            }

            try {
                this.animationCurrentStepValue = double.Parse(this.animationCurrentStepValueText.Text);
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.animationCurrentStepValueText,
                    "Current step value should be floating point number");
            }

            return unproperControl;
        }

        private UnproperControl? areAnimationParametersProper()
        {
            UnproperControl? unproperControl = null;

            try {
                this.animationStepValue = double.Parse(this.animationStepValueText.Text);
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.animationStepValueText,
                    "Step value should be floating point number");
            }

            try {
                uint fps = uint.Parse(this.animationFPSText.Text);
                if (fps <= 0) {
                    throw new FormatException();
                }
            } catch (FormatException) {
                unproperControl = new UnproperControl(this.animationFPSText,
                    "FSP value should be integer number greater than 0");
            }

            return unproperControl;
        }
        #endregion

        #region Getters and Setters
        private AbstractPerlinNoiseOperator getCurrentPerlinNoiseOperator()
        {
            return perlinNoiseTypeComboBox.SelectedIndex != -1 ? perlinNoiseOperators[perlinNoiseTypeComboBox.SelectedIndex] : null;
        }
        #endregion

        #region Initializers
        private void initializePerlinNoise()
        {
            this.perlinNoise = this.getCurrentPerlinNoiseOperator().createPerlinNoiseInstance(this.noiseGenerator, this.interpolator, this.smoother);
        }

        private void initializeNoiseGenerator(ConstructorInfo noiseGeneratorConstructor)
        {
            this.noiseGenerator = noiseGeneratorConstructor.Invoke(null) as AbstractNoiseGenerator;

            if (this.animationTimer == null || !this.animationTimer.Enabled && this.immediateUpdateCheckBox.Checked) {
                if (this.currentGenerateButton != null) {
                    this.currentGenerateButton.PerformClick();
                }
            } else {
                this.initializePerlinNoise();
            }
        }

        private void initializeInterpolator(ConstructorInfo interpolatorConstructor)
        {
            if (interpolatorConstructor != null) {
                this.interpolator = interpolatorConstructor.Invoke(null) as AbstractInterpolator;
            } else {
                this.interpolator = null;
            }

            if (this.animationTimer == null || !this.animationTimer.Enabled && this.immediateUpdateCheckBox.Checked) {
                if (this.currentGenerateButton != null) {
                    this.currentGenerateButton.PerformClick();
                }
            } else {
                this.initializePerlinNoise();
            }
        }

        private void initializeSmoother(ConstructorInfo smootherConstructor)
        {
            if (smootherConstructor != null) {
                this.smoother = smootherConstructor.Invoke(null) as AbstractSmoother;
            } else {
                this.smoother = null;
            }

            if (this.animationTimer == null || !this.animationTimer.Enabled && this.immediateUpdateCheckBox.Checked) {
                if (this.currentGenerateButton != null) {
                    this.currentGenerateButton.PerformClick();
                }
            } else {
                this.initializePerlinNoise();
            }
        }
        #endregion

        #region Animation control
        private void startAnimation()
        {
            UnproperControl? unproperControl = this.areParametersProper();
            if (unproperControl != null) {
                MessageBox.Show(this, unproperControl.Value.msg, "Animation", MessageBoxButtons.OK, MessageBoxIcon.Error);
                unproperControl.Value.control.Focus();
                return;
            }

            unproperControl = this.areAnimationParametersProper();
            if (unproperControl != null) {
                MessageBox.Show(this, unproperControl.Value.msg, "Animation", MessageBoxButtons.OK, MessageBoxIcon.Error);
                unproperControl.Value.control.Focus();
                return;
            }

            this.initializePerlinNoise();

            this.drawnFramesCounter = 0;
            this.checkDrawnFramesCounter = 0;
            this.currentFPS = 0.0d;

            uint fps = uint.Parse(this.animationFPSText.Text);

            this.perlinNoise1DDrawTexturePanel.Paint -= this.perlinNoise1DDrawTexturePanel_Paint;
            this.perlinNoise1DDrawGraphPanel.Paint -= this.perlinNoise1DDrawGraphPanel_Paint;
            this.perlinNoise2DDrawTexturePanel.Paint -= this.perlinNoise2DDrawTexturePanel_Paint;
            this.perlinNoise2DDrawTextureOnSpherePanel.Paint -= this.perlinNoise2DDrawTextureOnSpherePanel_Paint;
            this.perlinNoise2DDraw1DGraphPanel.Paint -= this.perlinNoise2DDraw1DGraph_Paint;
            this.perlinNoise3DDrawTexturePanel.Paint -= this.perlinNoise3DDrawTexturePanel_Paint;
            this.perlinNoise3DDraw2DTexturePanel.Paint -= this.perlinNoise3DDraw2DTexturePanel_Paint;

            this.animationTimer.Interval = (int)(1000.0d / fps);
            this.animationStartTicksCounter = DateTime.Now.Ticks;
            this.animationTimer.Start();
        }

        private void stopAnimation()
        {
            this.animationTimer.Stop();

            this.animationCurrentStepValueText.Text = this.animationCurrentStepValue.ToString();
 
            this.perlinNoise1DDrawTexturePanel.Paint += new PaintEventHandler(this.perlinNoise1DDrawTexturePanel_Paint);
            this.perlinNoise1DDrawGraphPanel.Paint += new PaintEventHandler(this.perlinNoise1DDrawGraphPanel_Paint);
            this.perlinNoise2DDrawTexturePanel.Paint += this.perlinNoise2DDrawTexturePanel_Paint;
            this.perlinNoise2DDrawTextureOnSpherePanel.Paint += this.perlinNoise2DDrawTextureOnSpherePanel_Paint;
            this.perlinNoise2DDraw1DGraphPanel.Paint += this.perlinNoise2DDraw1DGraph_Paint;
            this.perlinNoise3DDrawTexturePanel.Paint += this.perlinNoise3DDrawTexturePanel_Paint;
            this.perlinNoise3DDraw2DTexturePanel.Paint += this.perlinNoise3DDraw2DTexturePanel_Paint;

            this.currentGenerateButton.PerformClick();
        }
        #endregion

        #region Draw Perlin Noise 1D
        private void drawPerlinNoise1D_Texture(Bitmap drawBuffer, Range1D range, uint pointsNumber)
        {
            Range xRange = range.X;
            double xValue = xRange.Start;
            double stepValue = (xRange.End - xRange.Start) / (pointsNumber - 1);
            double widthStepValue = (double)drawBuffer.Width / pointsNumber;

            Graphics drawBufferGraphics = Graphics.FromImage(drawBuffer);
            Brush brush;

            // Clear everything
            drawBufferGraphics.Clear(Color.White);

            // Draw perlin noise texture
            for (uint i = 0; i < pointsNumber; ++i) {
                // Get perlin noise value
                double noiseValue = this.perlinNoise.perlinNoise1D(xValue);
                // Scale it to gray color
                int color = (int)(((noiseValue + 1.0d) / 2.0d) * 255);

                // Check boundaries
                if (color < 0) {
                    color = 0;
                } else if (color > 255) {
                    color = 255;
                }

                brush = new SolidBrush(Color.FromArgb(color, color, color));
                // Draw rectangles with calculated colors
                int rectX = (int)(i * widthStepValue);
                int rectWidth = (int)((i + 1) * widthStepValue) - rectX;
                drawBufferGraphics.FillRectangle(brush, rectX, 0, rectWidth, drawBuffer.Height);
                
                xValue += stepValue;
            }

            drawBufferGraphics.Dispose();
        }

        private void drawPerlinNoise1D_Graph(Bitmap drawBuffer, Range1D range, uint pointsNumber)
        {
            int drawingAreaWidth = drawBuffer.Width - 20;
            int drawingAreaHeight = drawBuffer.Height - 40;
            int startX = (drawBuffer.Width - drawingAreaWidth) / 2;
            int startY = (drawBuffer.Height - drawingAreaHeight) / 2;
            const int pointSize = 10;

            Range xRange = range.X;
            double xValue = xRange.Start;
            double stepValue = (xRange.End - xRange.Start) / (pointsNumber - 1);
            double widthStepValue = (double)drawingAreaWidth / (pointsNumber - 1);

            Graphics drawBufferGraphics = Graphics.FromImage(drawBuffer);
            Pen pen;
            Brush brush;

            // Clear everything
            drawBufferGraphics.Clear(Color.White);

            // Draw axis
            pen = new Pen(Color.Black, 3);
            drawBufferGraphics.DrawLine(pen, 0, drawBuffer.Height / 2, drawBuffer.Width - 1, drawBuffer.Height / 2);
            drawBufferGraphics.DrawLine(pen, drawBuffer.Width - 1, drawBuffer.Height / 2, drawBuffer.Width - 6, drawBuffer.Height / 2 - 5);
            drawBufferGraphics.DrawLine(pen, drawBuffer.Width - 1, drawBuffer.Height / 2, drawBuffer.Width - 6, drawBuffer.Height / 2 + 5);

            drawBufferGraphics.DrawLine(pen, drawBuffer.Width / 2, 0, drawBuffer.Width / 2, drawBuffer.Height - 1);
            drawBufferGraphics.DrawLine(pen, drawBuffer.Width / 2, 0, drawBuffer.Width / 2 - 5, 6);
            drawBufferGraphics.DrawLine(pen, drawBuffer.Width / 2, 0, drawBuffer.Width / 2 + 5, 6);

            // Draw x and y ranges
            pen = new Pen(Color.DarkGray, 1);
            drawBufferGraphics.DrawRectangle(pen, startX, startY, drawingAreaWidth, drawingAreaHeight);

            // Draw points
            pen = new Pen(Color.Black, 1);
            brush = new SolidBrush(Color.Red);
            for (uint i = 0; i < pointsNumber; ++i) {
                double noiseValue = this.perlinNoise.perlinNoise1D(xValue);

                int x = startX + (int)(i * widthStepValue);
                int y = drawBuffer.Height / 2 + (int)(noiseValue * drawingAreaHeight / 2);
                drawBufferGraphics.FillEllipse(brush, x - pointSize / 2, y - pointSize / 2, pointSize, pointSize);
                drawBufferGraphics.DrawEllipse(pen, x - pointSize / 2, y - pointSize / 2, pointSize, pointSize);

                xValue += stepValue;
            }

            drawBufferGraphics.Dispose();
        }

        private void perlinNoise1DDrawFrame()
        {
            Range1D perlinNoiseRange = new Range1D(new Range(
                this.valuesRange.X.Start + this.animationCurrentStepValue,
                this.valuesRange.X.End + this.animationCurrentStepValue));

            if (this.perlinNoise1DGenerateTextureCheckBox.Checked) {
                this.drawPerlinNoise1D_Texture(this.perlinNoise1DTextureBuffer, perlinNoiseRange, this.pointsNumber);
                this.perlinNoise1DDrawTexturePanel_Paint(this, null);
            }

            if (this.perlinNoise1DGenerateGraphCheckBox.Checked) {
                this.drawPerlinNoise1D_Graph(this.perlinNoise1DGraphBuffer, perlinNoiseRange, this.pointsNumber);
                this.perlinNoise1DDrawGraphPanel_Paint(this, null);
            }
        }
        #endregion

        #region Draw Perlin Noise 2D
        private void drawPerlinNoise2D_Texture(Bitmap drawBuffer, Range2D range, uint pointsNumber)
        {
            Range xRange = range.X;
            Range yRange = range.Y;
            double xValue = xRange.Start;
            double yValue = yRange.Start;
            double stepXValue = (xRange.End - xRange.Start) / (pointsNumber - 1);
            double stepYValue = (yRange.End - yRange.Start) / (pointsNumber - 1);
            double widthStepValue = (double)drawBuffer.Width / pointsNumber;
            double heightStepValue = (double)drawBuffer.Height / pointsNumber;

            Graphics drawBufferGraphics = Graphics.FromImage(drawBuffer);

            // Clear everything
            drawBufferGraphics.Clear(Color.White);

            // Lock bitmap
            BitmapData bufferData = drawBuffer.LockBits(new Rectangle(0, 0, drawBuffer.Width, drawBuffer.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

            // Needed variables
            double noiseValue;
            int color;
            int rectX;
            int rectWidth;
            int rectY;
            int rectHeight;

            // Draw perlin noise texture
            unsafe {
                byte* bufferPtr = (byte*)bufferData.Scan0;
                byte* currentBufferPtr = bufferPtr;
                byte* currentLineBufferPtr = bufferPtr;
                int bufferStride = bufferData.Stride;

                for (uint i = 0; i < pointsNumber; ++i) { // Iterate over y axis
                    // Reset xValue
                    xValue = xRange.Start;

                    for (uint j = 0; j < pointsNumber; ++j) { // Iterate over x axis
                        // Get perlin noise value
                        noiseValue = this.perlinNoise.perlinNoise2D(xValue, yValue);
                        
                        // Scale it to gray color
                        color = (int)(((noiseValue + 1.0d) / 2.0d) * 255);

                        // Check boundaries
                        if (color < 0) {
                            color = 0;
                        } else if (color > 255) {
                            color = 255;
                        }

                        // Draw rectangle with calculated color
                        rectX = (int)(j * widthStepValue);
                        rectWidth = (int)((j + 1) * widthStepValue) - rectX;
                        rectY = (int)(i * heightStepValue);
                        rectHeight = (int)((i + 1) * widthStepValue) - rectY;

                        // Calculate buffer location for first point
                        currentBufferPtr = bufferPtr + 3 * rectX + rectY * bufferStride;
                        currentLineBufferPtr = currentBufferPtr;

                        // Iterator over Y and X axis 
                        for (int buffY = 0; buffY < rectHeight; ++buffY) {
                            for (int buffX = 0; buffX < rectWidth; ++buffX) {
                                *currentLineBufferPtr = (byte)color;
                                currentLineBufferPtr++;
                                *currentLineBufferPtr = (byte)color;
                                currentLineBufferPtr++;
                                *currentLineBufferPtr = (byte)color;
                                currentLineBufferPtr++;
                            }

                            // Move buffer ptr to next line
                            currentBufferPtr += bufferStride;
                            currentLineBufferPtr = currentBufferPtr;
                        }

                        // Increate xValue by step
                        xValue += stepXValue;
                    }
                    
                    // Increate yValue by step
                    yValue += stepYValue;
                }
            }

            // Clean up
            drawBuffer.UnlockBits(bufferData);
            drawBufferGraphics.Dispose();
        }

        private void drawPerlinNoise2D_TextureOnSphere(Bitmap drawBuffer, Range2D range)
        {
            // Calculate perlin noise first coord and step
            Range xRange = range.X;
            Range yRange = range.Y;
            double xValue = xRange.Start;
            double yValue = yRange.Start;
            double xRangeDiff = xRange.End - xRange.Start;
            double yRangeDiff = yRange.End - yRange.Start;

            // Calculate first space coord and step
            double xCoord = -1.0d;
            double yCoord = -1.0d;
            double zCoord;
            double xCoordStep = 2.0d / (drawBuffer.Width - 1);
            double yCoordStep = 2.0d / (drawBuffer.Height - 1);

            // Set north vector and toEquatorVector
            const double northVectorX = 0.0d;
            const double northVectorY = 1.0d;
            const double northVectorZ = 0.0d;
            const double toEquatorVectorX = 1.0d;
            const double toEquatorVectorY = 0.0d;
            const double toEquatorVectorZ = 0.0d;
            const double crossVectorX = northVectorY * toEquatorVectorZ - toEquatorVectorY * northVectorZ;
            const double crossVectorY = northVectorZ * toEquatorVectorX - toEquatorVectorZ * northVectorX;
            const double crossVectorZ = northVectorX * toEquatorVectorY - toEquatorVectorX * northVectorY;

            // Get graphics object for buffer
            Graphics drawBufferGraphics = Graphics.FromImage(drawBuffer);

            // Lock bitmap
            BitmapData bufferData = drawBuffer.LockBits(new Rectangle(0, 0, drawBuffer.Width, drawBuffer.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

            // Needed variables
            double sqrXCoord;
            double sqrYCoord;
            double sumSqrXYCoords;
            double phi;
            double theta;
            double u;
            double v;
            double noiseValue;
            int color;

            // Draw texture
            unsafe {
                byte* bufferPtr = (byte*)bufferData.Scan0;
                byte* currentBufferPtr = bufferPtr;
                byte* currentLineBufferPtr = bufferPtr;
                int bufferStride = bufferData.Stride;

                for (int y = 0; y < drawBuffer.Height; ++y) { // Iterate over y axis
                    // Reset xCoord
                    xCoord = -1.0;

                    for (int x = 0; x < drawBuffer.Width; ++x) { // Iterate over x axis
                        // Calculate circle equation variables
                        sqrXCoord = xCoord * xCoord;
                        sqrYCoord = yCoord * yCoord;
                        sumSqrXYCoords = sqrXCoord + sqrYCoord;

                        if (sumSqrXYCoords <= 1.0d) { // If point is on boundary or inside the circle...
                            // Calculate zCoord from sphere equation
                            zCoord = Math.Sqrt(1.0d - sumSqrXYCoords);

                            // Calculate UV coords
                            phi = Math.Acos(-Vector.DotProduct(northVectorX, northVectorY, northVectorZ, xCoord, yCoord, zCoord));
                            theta = Math.Acos(-Vector.DotProduct(toEquatorVectorX, toEquatorVectorY, toEquatorVectorZ, xCoord, yCoord, zCoord) / Math.Sin(phi)) / (2.0d * Math.PI);
                            if (Vector.DotProduct(crossVectorX, crossVectorY, crossVectorZ, xCoord, yCoord, zCoord) > 0.0d) {
                                u = theta;
                            } else {
                                u = 1.0d - theta;
                            }
                            v = phi / Math.PI;

                            // Get perlin noise value
                            noiseValue = this.perlinNoise.perlinNoise2D(xValue + xRangeDiff * u, yValue + yRangeDiff * v);

                            // Scale it to gray color
                            color = (int)(((noiseValue + 1.0d) / 2.0d) * 255);

                            // Check boundaries
                            if (color < 0) {
                                color = 0;
                            } else if (color > 255) {
                                color = 255;
                            }

                            // Draw point with given color
                            *currentLineBufferPtr = (byte)color;
                            currentLineBufferPtr++;
                            *currentLineBufferPtr = (byte)color;
                            currentLineBufferPtr++;
                            *currentLineBufferPtr = (byte)color;
                            currentLineBufferPtr++;
                        } else {
                            // Draw white point, as point is outside the circle
                            *currentLineBufferPtr = (byte)255;
                            currentLineBufferPtr++;
                            *currentLineBufferPtr = (byte)255;
                            currentLineBufferPtr++;
                            *currentLineBufferPtr = (byte)255;
                            currentLineBufferPtr++;
                        }
                        // Increate xCoord by step
                        xCoord += xCoordStep;
                    }

                    // Move buffer ptr to next line
                    currentBufferPtr += bufferStride;
                    currentLineBufferPtr = currentBufferPtr;

                    // Increate yCoord by step
                    yCoord += yCoordStep;
                }
            }

            // Clean up
            drawBuffer.UnlockBits(bufferData);
            drawBufferGraphics.Dispose();
        }

        private void drawPerlinNoise2D_1DGraph(Bitmap drawBuffer, Range2D range, uint pointsNumber)
        {
            int drawingAreaWidth = drawBuffer.Width - 20;
            int drawingAreaHeight = drawBuffer.Height - 40;
            int startX = (drawBuffer.Width - drawingAreaWidth) / 2;
            int startY = (drawBuffer.Height - drawingAreaHeight) / 2;
            const int pointSize = 10;

            Range xRange = range.X;
            Range yRange = range.Y;
            double xValue = xRange.Start;
            double yValue = yRange.Start;
            double xStepValue = (xRange.End - xRange.Start) / (pointsNumber - 1);
            double yStepValue = (yRange.End - yRange.Start) / (pointsNumber - 1);
            double widthStepValue = (double)drawingAreaWidth / (pointsNumber - 1);

            Graphics drawBufferGraphics = Graphics.FromImage(drawBuffer);
            Pen pen;
            Brush brush;

            // Clear everything
            drawBufferGraphics.Clear(Color.White);

            // Draw axis
            pen = new Pen(Color.Black, 3);
            drawBufferGraphics.DrawLine(pen, 0, drawBuffer.Height / 2, drawBuffer.Width - 1, drawBuffer.Height / 2);
            drawBufferGraphics.DrawLine(pen, drawBuffer.Width - 1, drawBuffer.Height / 2, drawBuffer.Width - 6, drawBuffer.Height / 2 - 5);
            drawBufferGraphics.DrawLine(pen, drawBuffer.Width - 1, drawBuffer.Height / 2, drawBuffer.Width - 6, drawBuffer.Height / 2 + 5);

            drawBufferGraphics.DrawLine(pen, drawBuffer.Width / 2, 0, drawBuffer.Width / 2, drawBuffer.Height - 1);
            drawBufferGraphics.DrawLine(pen, drawBuffer.Width / 2, 0, drawBuffer.Width / 2 - 5, 6);
            drawBufferGraphics.DrawLine(pen, drawBuffer.Width / 2, 0, drawBuffer.Width / 2 + 5, 6);

            // Draw x and y ranges
            pen = new Pen(Color.DarkGray, 1);
            drawBufferGraphics.DrawRectangle(pen, startX, startY, drawingAreaWidth, drawingAreaHeight);

            // Draw points
            pen = new Pen(Color.Black, 1);
            brush = new SolidBrush(Color.Red);
            for (uint i = 0; i < pointsNumber; ++i) {
                double noiseValue = this.perlinNoise.perlinNoise2D(xValue, yValue);

                int x = startX + (int)(i * widthStepValue);
                int y = drawBuffer.Height / 2 - (int)(noiseValue * drawingAreaHeight / 2);
                drawBufferGraphics.FillEllipse(brush, x - pointSize / 2, y - pointSize / 2, pointSize, pointSize);
                drawBufferGraphics.DrawEllipse(pen, x - pointSize / 2, y - pointSize / 2, pointSize, pointSize);

                xValue += xStepValue;
                yValue += yStepValue;
            }

            drawBufferGraphics.Dispose();
        }

        private void perlinNoise2DDrawFrame()
        {
            Range2D perlinNoiseRange = new Range2D(
                new Range(
                    this.valuesRange.X.Start + this.animationCurrentStepValue,
                    this.valuesRange.X.End + this.animationCurrentStepValue),
                new Range(
                    this.valuesRange.Y.Start + this.animationCurrentStepValue,
                    this.valuesRange.Y.End + this.animationCurrentStepValue));

            if (this.perlinNoise2DGenerateTextureCheckBox.Checked) {
                this.drawPerlinNoise2D_Texture(this.perlinNoise2DTextureBuffer, perlinNoiseRange, this.pointsNumber);
                this.perlinNoise2DDrawTexturePanel_Paint(this, null);
            }

            if (this.perlinNoise2DGenerateTextureOnSphereCheckBox.Checked) {
                this.drawPerlinNoise2D_TextureOnSphere(this.perlinNoise2DTextureOnSphereBuffer, perlinNoiseRange);
                this.perlinNoise2DDrawTextureOnSpherePanel_Paint(this, null);
            }

            if (this.perlinNoise2DGenerate1DGraphCheckBox.Checked) {
                this.drawPerlinNoise2D_1DGraph(this.perlinNoise2D1DGraphBuffer, perlinNoiseRange, this.pointsNumber);
                this.perlinNoise2DDraw1DGraph_Paint(this, null);
            }
        }
        #endregion

        #region Draw Perlin Noise 3D
        private void drawPerlinNoise3D_Texture(Bitmap drawBuffer, Range3D range)
        {
            // Calculate perlin noise first coord and step
            Range xRange = range.X;
            Range yRange = range.Y;
            Range zRange = range.Z;
            double xValue = xRange.Start;
            double yValue = yRange.Start;
            double zValue = zRange.Start;
            double xRangeDiff = xRange.End - xRange.Start;
            double yRangeDiff = yRange.End - yRange.Start;
            double zRangeDiff = zRange.End - zRange.Start;
            double stepXValue = xRangeDiff / (drawBuffer.Width - 1);
            double stepYValue = yRangeDiff / (drawBuffer.Height - 1);

            // Calculate first space coord and step
            double xCoord = -1.0d;
            double yCoord = -1.0d;
            double zCoord;
            double xCoordStep = 2.0d / (drawBuffer.Width - 1);
            double yCoordStep = 2.0d / (drawBuffer.Height - 1);

            // Get graphics object for buffer
            Graphics drawBufferGraphics = Graphics.FromImage(drawBuffer);

            // Lock bitmap
            BitmapData bufferData = drawBuffer.LockBits(new Rectangle(0, 0, drawBuffer.Width, drawBuffer.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

            // Needed variables
            double sqrXCoord;
            double sqrYCoord;
            double sumSqrXYCoords;
            double noiseValue;
            int color;

            // Draw texture
            unsafe {
                byte* bufferPtr = (byte*)bufferData.Scan0;
                byte* currentBufferPtr = bufferPtr;
                byte* currentLineBufferPtr = bufferPtr;
                int bufferStride = bufferData.Stride;

                for (int y = 0; y < drawBuffer.Height; ++y) { // Iterate over y axis
                    // Reset xCoord
                    xCoord = -1.0;
                    xValue = xRange.Start;

                    for (int x = 0; x < drawBuffer.Width; ++x) { // Iterate over x axis
                        // Calculate circle equation variables
                        sqrXCoord = xCoord * xCoord;
                        sqrYCoord = yCoord * yCoord;
                        sumSqrXYCoords = sqrXCoord + sqrYCoord;

                        if (sumSqrXYCoords <= 1.0d) { // If point is on boundary or inside the circle...
                            // Calculate zCoord from sphere equation
                            zCoord = Math.Sqrt(1.0d - sumSqrXYCoords);

                            // Get perlin noise value
                            noiseValue = this.perlinNoise.perlinNoise3D(xValue, yValue, zValue + zCoord * zRangeDiff);

                            // Scale it to gray color
                            color = (int)(((noiseValue + 1.0d) / 2.0d) * 255);

                            // Check boundaries
                            if (color < 0) {
                                color = 0;
                            } else if (color > 255) {
                                color = 255;
                            }

                            // Draw point with given color
                            *currentLineBufferPtr = (byte)color;
                            currentLineBufferPtr++;
                            *currentLineBufferPtr = (byte)color;
                            currentLineBufferPtr++;
                            *currentLineBufferPtr = (byte)color;
                            currentLineBufferPtr++;
                        } else {
                            // Draw white point, as point is outside the circle
                            *currentLineBufferPtr = (byte)255;
                            currentLineBufferPtr++;
                            *currentLineBufferPtr = (byte)255;
                            currentLineBufferPtr++;
                            *currentLineBufferPtr = (byte)255;
                            currentLineBufferPtr++;
                        }
                        // Increate xCoord by step
                        xCoord += xCoordStep;
                        xValue += stepXValue;
                    }

                    // Move buffer ptr to next line
                    currentBufferPtr += bufferStride;
                    currentLineBufferPtr = currentBufferPtr;

                    // Increate yCoord by step
                    yCoord += yCoordStep;
                    yValue += stepYValue;
                }
            }

            // Clean up
            drawBuffer.UnlockBits(bufferData);
            drawBufferGraphics.Dispose();
        }

        private void drawPerlinNoise3D_2DTexture(Bitmap drawBuffer, Range3D range, uint pointsNumber)
        {
            Range xRange = range.X;
            Range yRange = range.Y;
            Range zRange = range.Z;
            double xValue = xRange.Start;
            double yValue = yRange.Start;
            double zValue = zRange.Start;
            double stepXValue = (xRange.End - xRange.Start) / (pointsNumber - 1);
            double stepYValue = (yRange.End - yRange.Start) / (pointsNumber - 1);
            double stepZValue = (zRange.End - zRange.Start) / (pointsNumber - 1);
            double widthStepValue = (double)drawBuffer.Width / pointsNumber;
            double heightStepValue = (double)drawBuffer.Height / pointsNumber;

            Graphics drawBufferGraphics = Graphics.FromImage(drawBuffer);

            // Clear everything
            drawBufferGraphics.Clear(Color.White);

            // Lock bitmap
            BitmapData bufferData = drawBuffer.LockBits(new Rectangle(0, 0, drawBuffer.Width, drawBuffer.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

            // Needed variables
            double noiseValue;
            int color;
            int rectX;
            int rectWidth;
            int rectY;
            int rectHeight;

            // Draw perlin noise texture
            unsafe {
                byte* bufferPtr = (byte*)bufferData.Scan0;
                byte* currentBufferPtr = bufferPtr;
                byte* currentLineBufferPtr = bufferPtr;
                int bufferStride = bufferData.Stride;

                for (uint i = 0; i < pointsNumber; ++i) { // Iterate over y axis
                    // Reset xValue
                    xValue = xRange.Start;

                    for (uint j = 0; j < pointsNumber; ++j) { // Iterate over x axis
                        // Get perlin noise value
                        noiseValue = this.perlinNoise.perlinNoise3D(xValue, yValue, zValue);

                        // Scale it to gray color
                        color = (int)(((noiseValue + 1.0d) / 2.0d) * 255);

                        // Check boundaries
                        if (color < 0) {
                            color = 0;
                        } else if (color > 255) {
                            color = 255;
                        }

                        // Draw rectangle with calculated color
                        rectX = (int)(j * widthStepValue);
                        rectWidth = (int)((j + 1) * widthStepValue) - rectX;
                        rectY = (int)(i * heightStepValue);
                        rectHeight = (int)((i + 1) * widthStepValue) - rectY;

                        // Calculate buffer location for first point
                        currentBufferPtr = bufferPtr + 3 * rectX + rectY * bufferStride;
                        currentLineBufferPtr = currentBufferPtr;

                        // Iterator over Y and X axis 
                        for (int buffY = 0; buffY < rectHeight; ++buffY) {
                            for (int buffX = 0; buffX < rectWidth; ++buffX) {
                                *currentLineBufferPtr = (byte)color;
                                currentLineBufferPtr++;
                                *currentLineBufferPtr = (byte)color;
                                currentLineBufferPtr++;
                                *currentLineBufferPtr = (byte)color;
                                currentLineBufferPtr++;
                            }

                            // Move buffer ptr to next line
                            currentBufferPtr += bufferStride;
                            currentLineBufferPtr = currentBufferPtr;
                        }

                        // Increate xValue by step
                        xValue += stepXValue;
                    }

                    // Increate yValue by step
                    yValue += stepYValue;
                }
            }

            // Clean up
            drawBuffer.UnlockBits(bufferData);
            drawBufferGraphics.Dispose();
        }

        private void perlinNoise3DDrawFrame()
        {
            Range3D perlinNoiseRange = new Range3D(
                new Range(
                    this.valuesRange.X.Start + this.animationCurrentStepValue,
                    this.valuesRange.X.End + this.animationCurrentStepValue),
                new Range(
                    this.valuesRange.Y.Start + this.animationCurrentStepValue,
                    this.valuesRange.Y.End + this.animationCurrentStepValue),
                new Range(
                    this.valuesRange.Z.Start + this.animationCurrentStepValue,
                    this.valuesRange.Z.End + this.animationCurrentStepValue));

            if (this.perlinNoise3DGenerateTextureCheckBox.Checked) {
                this.drawPerlinNoise3D_Texture(this.perlinNoise3DTextureBuffer, perlinNoiseRange);
                this.perlinNoise3DDrawTexturePanel_Paint(this, null);
            }

            if (this.perlinNoise3DGenerate2DTextureCheckBox.Checked) {
                this.drawPerlinNoise3D_2DTexture(this.perlinNoise3D2DTextureBuffer, perlinNoiseRange, this.pointsNumber);
                this.perlinNoise3DDraw2DTexturePanel_Paint(this, null);
            }
        }
        #endregion

        #region Perlin Noise 1D Events
        private void perlinNoise1DGenerateButton_Click(object sender, EventArgs evt)
        {
            UnproperControl? unproperControl = this.arePN1DParametersProper();
            if (unproperControl == null) {
                this.initializePerlinNoise();

                Range1D perlinNoiseRange = new Range1D(new Range(
                    this.valuesRange.X.Start + this.animationCurrentStepValue,
                    this.valuesRange.X.End + this.animationCurrentStepValue));

                if (this.perlinNoise1DGenerateTextureCheckBox.Checked && this.perlinNoise1DTextureBuffer != null) {
                    this.drawPerlinNoise1D_Texture(this.perlinNoise1DTextureBuffer, perlinNoiseRange, this.pointsNumber);
                    this.perlinNoise1DDrawTexturePanel.Invalidate();
                }

                if (this.perlinNoise1DGenerateGraphCheckBox.Checked && this.perlinNoise1DGraphBuffer != null) {
                    this.drawPerlinNoise1D_Graph(this.perlinNoise1DGraphBuffer, perlinNoiseRange, this.pointsNumber);
                    this.perlinNoise1DDrawGraphPanel.Invalidate();
                }
            } else {
                MessageBox.Show(this, unproperControl.Value.msg, "Perlin Noise 1D", MessageBoxButtons.OK, MessageBoxIcon.Error);
                unproperControl.Value.control.Focus();
            }
        }
        
        private void perlinNoise1DDrawTexturePanel_Paint(object sender, PaintEventArgs evt)
        {
            if (this.perlinNoise1DTextureBuffer != null) {
                Graphics texturePanelGraphics = this.perlinNoise1DDrawTexturePanel.CreateGraphics();
                texturePanelGraphics.DrawImageUnscaled(this.perlinNoise1DTextureBuffer, 0, 0);
                texturePanelGraphics.Dispose();
            }
        }

        private void perlinNoise1DDrawGraphPanel_Paint(object sender, PaintEventArgs evt)
        {
            if (this.perlinNoise1DGraphBuffer != null) {
                Graphics graphPanelGraphics = this.perlinNoise1DDrawGraphPanel.CreateGraphics();
                graphPanelGraphics.DrawImageUnscaled(this.perlinNoise1DGraphBuffer, 0, 0);
                graphPanelGraphics.Dispose();
            }
        }
        #endregion

        #region Perlin Noise 2D Events
        private void perlinNoise2DGenerateButton_Click(object sender, EventArgs evt)
        {
            UnproperControl? unproperControl = this.arePN2DParametersProper();
            if (unproperControl == null) {
                this.initializePerlinNoise();

                Range2D perlinNoiseRange = new Range2D(
                    new Range(
                        this.valuesRange.X.Start + this.animationCurrentStepValue,
                        this.valuesRange.X.End + this.animationCurrentStepValue),
                    new Range(
                        this.valuesRange.Y.Start + this.animationCurrentStepValue,
                        this.valuesRange.Y.End + this.animationCurrentStepValue));

                if (this.perlinNoise2DGenerateTextureCheckBox.Checked && this.perlinNoise2DTextureBuffer != null) {
                    this.drawPerlinNoise2D_Texture(this.perlinNoise2DTextureBuffer, perlinNoiseRange, pointsNumber);
                    this.perlinNoise2DDrawTexturePanel.Invalidate();
                }

                if (this.perlinNoise2DGenerateTextureOnSphereCheckBox.Checked && this.perlinNoise2DTextureOnSphereBuffer != null) {
                    this.drawPerlinNoise2D_TextureOnSphere(this.perlinNoise2DTextureOnSphereBuffer, perlinNoiseRange);
                    this.perlinNoise2DDrawTextureOnSpherePanel.Invalidate();
                }

                if (this.perlinNoise2DGenerate1DGraphCheckBox.Checked && this.perlinNoise2D1DGraphBuffer != null) {
                    this.drawPerlinNoise2D_1DGraph(this.perlinNoise2D1DGraphBuffer, perlinNoiseRange, pointsNumber);
                    this.perlinNoise2DDraw1DGraphPanel.Invalidate();
                }
            } else {
                MessageBox.Show(this, unproperControl.Value.msg, "Perlin Noise 2D", MessageBoxButtons.OK, MessageBoxIcon.Error);
                unproperControl.Value.control.Focus();
            }
        }

        private void perlinNoise2DDrawTexturePanel_Paint(object sender, PaintEventArgs evt)
        {
            if (this.perlinNoise2DTextureBuffer != null) {
                Graphics texturePanelGraphics = this.perlinNoise2DDrawTexturePanel.CreateGraphics();
                texturePanelGraphics.DrawImageUnscaled(this.perlinNoise2DTextureBuffer, 0, 0);
                texturePanelGraphics.Dispose();
            }
        }

        private void perlinNoise2DDrawTextureOnSpherePanel_Paint(object sender, PaintEventArgs evt)
        {
            if (this.perlinNoise2DTextureOnSphereBuffer != null) {
                Graphics graphPanelGraphics = this.perlinNoise2DDrawTextureOnSpherePanel.CreateGraphics();
                graphPanelGraphics.DrawImageUnscaled(this.perlinNoise2DTextureOnSphereBuffer, 0, 0);
                graphPanelGraphics.Dispose();
            }
        }

        private void perlinNoise2DDraw1DGraph_Paint(object sender, PaintEventArgs evt)
        {
            if (this.perlinNoise2D1DGraphBuffer != null) {
                Graphics graphPanelGraphics = this.perlinNoise2DDraw1DGraphPanel.CreateGraphics();
                graphPanelGraphics.DrawImageUnscaled(this.perlinNoise2D1DGraphBuffer, 0, 0);
                graphPanelGraphics.Dispose();
            }
        }
        #endregion

        #region Perlin Noise 3D Events
        private void perlinNoise3DGenerateButton_Click(object sender, EventArgs evt)
        {
            UnproperControl? unproperControl = this.arePN3DParametersProper();
            if (unproperControl == null) {
                this.initializePerlinNoise();

                Range3D perlinNoiseRange = new Range3D(
                    new Range(
                        this.valuesRange.X.Start + this.animationCurrentStepValue,
                        this.valuesRange.X.End + this.animationCurrentStepValue),
                    new Range(
                        this.valuesRange.Y.Start + this.animationCurrentStepValue,
                        this.valuesRange.Y.End + this.animationCurrentStepValue),
                    new Range(
                        this.valuesRange.Z.Start + this.animationCurrentStepValue,
                        this.valuesRange.Z.End + this.animationCurrentStepValue));

                if (this.perlinNoise3DGenerateTextureCheckBox.Checked && this.perlinNoise3DTextureBuffer != null) {
                    this.drawPerlinNoise3D_Texture(this.perlinNoise3DTextureBuffer, perlinNoiseRange);
                    this.perlinNoise3DDrawTexturePanel.Invalidate();
                }

                if (this.perlinNoise3DGenerate2DTextureCheckBox.Checked && this.perlinNoise3DTextureBuffer != null) {
                    this.drawPerlinNoise3D_2DTexture(this.perlinNoise3D2DTextureBuffer, perlinNoiseRange, pointsNumber);
                    this.perlinNoise3DDraw2DTexturePanel.Invalidate();
                }
            } else {
                MessageBox.Show(this, unproperControl.Value.msg, "Perlin Noise 3D", MessageBoxButtons.OK, MessageBoxIcon.Error);
                unproperControl.Value.control.Focus();
            }
        }

        private void perlinNoise3DDrawTexturePanel_Paint(object sender, PaintEventArgs evt)
        {
            if (this.perlinNoise3DTextureBuffer != null) {
                Graphics texturePanelGraphics = this.perlinNoise3DDrawTexturePanel.CreateGraphics();
                texturePanelGraphics.DrawImageUnscaled(this.perlinNoise3DTextureBuffer, 0, 0);
                texturePanelGraphics.Dispose();
            }
        }

        private void perlinNoise3DDraw2DTexturePanel_Paint(object sender, PaintEventArgs evt)
        {
            if (this.perlinNoise3D2DTextureBuffer != null) {
                Graphics texturePanelGraphics = this.perlinNoise3DDraw2DTexturePanel.CreateGraphics();
                texturePanelGraphics.DrawImageUnscaled(this.perlinNoise3D2DTextureBuffer, 0, 0);
                texturePanelGraphics.Dispose();
            }
        }
        #endregion

        #region Perlin Noise Controls General Events
        private void dimensionTabs_Selected(object sender, EventArgs evt)
        {
            switch (this.dimensionTabs.SelectedIndex) {
                case 0:
                    this.currentGenerateButton = this.perlinNoise1DGenerateButton;
                    this.areParametersProper = this.arePN1DParametersProper;
                    this.perlinNoiseDrawFrame = this.perlinNoise1DDrawFrame;
                    break;
                case 1:
                    this.currentGenerateButton = this.perlinNoise2DGenerateButton;
                    this.areParametersProper = this.arePN2DParametersProper;
                    this.perlinNoiseDrawFrame = this.perlinNoise2DDrawFrame;
                    break;
                case 2:
                    this.currentGenerateButton = this.perlinNoise3DGenerateButton;
                    this.areParametersProper = this.arePN3DParametersProper;
                    this.perlinNoiseDrawFrame = this.perlinNoise3DDrawFrame;
                    break;
            }
        }

        private void perlinNoiseTypeComboBox_SelectedIndexChanged(object sender, EventArgs evt)
        {
            if (this.animationTimer == null || !this.animationTimer.Enabled && this.immediateUpdateCheckBox.Checked) {
                if (this.currentGenerateButton != null) {
                    this.currentGenerateButton.PerformClick();
                }
            } else {
                this.initializePerlinNoise();
            }
        }

        private void perlinNoiseTypeOptionsButton_Click(object sender, EventArgs evt)
        {
            AbstractPerlinNoiseOperator perlinNoiseOperator = this.getCurrentPerlinNoiseOperator();
            if (perlinNoiseOperator.showOptionsForm(this) && !this.animationTimer.Enabled) {
                if (this.currentGenerateButton != null) {
                    this.currentGenerateButton.PerformClick();
                }
            } else {
                this.initializePerlinNoise();
            }
        }

        private void noiseGeneratorComboBox_SelectedIndexChanged(object sender, EventArgs evt)
        {
            if (noiseGeneratorComboBox.SelectedIndex != -1) {
                this.initializeNoiseGenerator(noiseGeneratorTypes[noiseGeneratorComboBox.SelectedIndex].GetConstructor(Type.EmptyTypes));
            }
        }

        private void noiseGeneratorOptionsButton_Click(object sender, EventArgs evt)
        {

        }

        private void interpolatorComboBox_SelectedIndexChanged(object sender, EventArgs evt)
        {
            if (interpolatorComboBox.SelectedIndex != -1) {
                Type interpolatorType = interpolatorTypes[interpolatorComboBox.SelectedIndex];
                if (interpolatorType != null) {
                    this.initializeInterpolator(interpolatorType.GetConstructor(Type.EmptyTypes));
                } else {
                    this.initializeInterpolator(null);
                }
            }
        }

        private void interpolatorOptionsButton_Click(object sender, EventArgs evt)
        {

        }

        private void smootherComboBox_SelectedIndexChanged(object sender, EventArgs evt)
        {
            if (smootherComboBox.SelectedIndex != -1) {
                Type smootherType = smootherTypes[smootherComboBox.SelectedIndex];
                if (smootherType != null) {
                    this.initializeSmoother(smootherType.GetConstructor(Type.EmptyTypes));
                } else {
                    this.initializeSmoother(null);
                }
            }
        }

        private void smootherOptionsButton_Click(object sender, EventArgs evt)
        {

        }

        private void immediateUpdateCheckBox_CheckedChanged(object sender, EventArgs evt)
        {
            if (this.immediateUpdateCheckBox.Checked && (this.animationTimer == null || !this.animationTimer.Enabled)) {
                if (this.currentGenerateButton != null) {
                    this.currentGenerateButton.PerformClick();
                }
            }
        }
        #endregion

        #region Animation Events
        private void animationTimerTick(object sender, EventArgs evt)
        {
            this.animationCurrentStepValueText.Text = this.animationCurrentStepValue.ToString();
            long t1 = DateTime.Now.Ticks;
            this.perlinNoiseDrawFrame();
            long t2 = DateTime.Now.Ticks;
            this.animationCurrentStepValue += this.animationStepValue;

            this.drawnFramesCounter++;
            this.checkDrawnFramesCounter++;

            TimeSpan frameCalculationTimeSpan = new TimeSpan(t2 - t1);
            this.lastCheckTimeSpan += frameCalculationTimeSpan;
            double frameCalculationTimeSpanMS = frameCalculationTimeSpan.TotalMilliseconds;
            double framesCalculationTimeSpanMS = this.lastCheckTimeSpan.TotalMilliseconds;

            this.drawnFramesTextBox.Text = this.drawnFramesCounter.ToString();
            this.usedTimeTextBox.Text = String.Format("{0:F}ms", frameCalculationTimeSpanMS);

            if (frameCalculationTimeSpanMS > 0) {
                if (framesCalculationTimeSpanMS >= 1000) {
                    this.currentFPS = this.checkDrawnFramesCounter / (framesCalculationTimeSpanMS / 1000.0);
                    this.checkDrawnFramesCounter = 0;
                    this.lastCheckTimeSpan = TimeSpan.Zero;
                }

                this.currentFPSTextBox.Text = String.Format("{0:F} FPS", this.currentFPS);
            } else {
                long globalTicksCounter = t2 - this.animationStartTicksCounter;

                if (globalTicksCounter > 0) {
                    this.currentFPS = this.drawnFramesCounter / (new TimeSpan(globalTicksCounter).TotalMilliseconds / 1000.0);

                    this.currentFPSTextBox.Text = String.Format("{0:F} FPS", this.currentFPS);
                } else {
                    this.currentFPSTextBox.Text = "Too fast to calculate";
                }
            }
        }

        private void animationStartStopButton_Click(object sender, EventArgs evt)
        {
            if (this.animationTimer.Enabled) {
                this.animationStartStopButton.Text = "Start animation";
                this.stopAnimation();
            } else {
                this.animationStartStopButton.Text = "Stop animation";
                this.startAnimation();
            }

            this.perlinNoise1DStartValueText.Enabled = !this.animationTimer.Enabled;
            this.perlinNoise1DEndValueText.Enabled = !this.animationTimer.Enabled;
            this.perlinNoise1DPointsNumberText.Enabled = !this.animationTimer.Enabled;
            this.perlinNoise1DGenerateButton.Enabled = !this.animationTimer.Enabled;
            this.perlinNoise2DStartXValueText.Enabled = !this.animationTimer.Enabled;
            this.perlinNoise2DEndXValueText.Enabled = !this.animationTimer.Enabled;
            this.perlinNoise2DStartYValueText.Enabled = !this.animationTimer.Enabled;
            this.perlinNoise2DEndYValueText.Enabled = !this.animationTimer.Enabled;
            this.perlinNoise2DPointsNumberText.Enabled = !this.animationTimer.Enabled;
            this.perlinNoise3DGenerateButton.Enabled = !this.animationTimer.Enabled;
            this.perlinNoise3DStartXValueText.Enabled = !this.animationTimer.Enabled;
            this.perlinNoise3DEndXValueText.Enabled = !this.animationTimer.Enabled;
            this.perlinNoise3DStartYValueText.Enabled = !this.animationTimer.Enabled;
            this.perlinNoise3DEndYValueText.Enabled = !this.animationTimer.Enabled;
            this.perlinNoise3DStartZValueText.Enabled = !this.animationTimer.Enabled;
            this.perlinNoise3DEndZValueText.Enabled = !this.animationTimer.Enabled;
            this.perlinNoise3DPointsNumberText.Enabled = !this.animationTimer.Enabled;
            this.perlinNoise3DGenerateButton.Enabled = !this.animationTimer.Enabled;
            this.animationCurrentStepValueText.Enabled = !this.animationTimer.Enabled;
            this.animationStepValueText.Enabled = !this.animationTimer.Enabled;
            this.animationFPSText.Enabled = !this.animationTimer.Enabled;
            this.animationPrevFrameButton.Enabled = !this.animationTimer.Enabled;
            this.animationNextFrameButton.Enabled = !this.animationTimer.Enabled;
            this.immediateUpdateCheckBox.Enabled = !this.animationTimer.Enabled;
        }

        private void animationPrevFrameButton_Click(object sender, EventArgs evt)
        {
            if (!this.animationTimer.Enabled) {
                this.animationCurrentStepValue -= this.animationStepValue;
                this.animationCurrentStepValueText.Text = this.animationCurrentStepValue.ToString();
                this.perlinNoiseDrawFrame();
            }
        }

        private void animationNextFrameButton_Click(object sender, EventArgs evt)
        {
            if (!this.animationTimer.Enabled) {
                this.animationCurrentStepValue += this.animationStepValue;
                this.animationCurrentStepValueText.Text = this.animationCurrentStepValue.ToString();
                this.perlinNoiseDrawFrame();
            }
        }
        #endregion
    }
}